package com.mystudio.android.calendar.view;


import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.format.Time;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.mystudio.android.calendar.constants.Constants;
import com.mystudio.android.calendar.model.CalendarConfig;
import com.mystudio.android.calendar.model.Instance;
import com.mystudio.android.calendar.util.LunarCalendar;
import com.mystudio.android.util.Logger;

public class WeekInMonthView extends View {
	private static final Logger logger = new Logger(Constants.LOG_TAG, WeekInMonthView.class);
	
	private CalendarConfig config;
	//private static final int CALENDAR_PAGE_WEEKS = 5;

    //private Time mViewCalendar;
    //private Time mToday;
    
    /**
     * The first day of week in default locale
     */
    //private int mStartDay;
    
    /**
     * The first Julian day of the current week.
     */
    public int mFirstJulianDay = 0;

    /**
     * The top left Julian day of the top bitmap page.
     */
    //private int mTopLeftJulianDayOfTop;
    //private int mTopLeftJulianDayOfBottom = 0;
    private int mCurrentMonth;

    // For drawing to an off-screen Canvas
    private Bitmap mBitmap;
    private Canvas mCanvas;
    //private Bitmap mBitmap2;
    //private Canvas mCanvas2;
    private boolean mRedrawScreen = true;
    private boolean mDrawEvents = false;
    //private Rect mBitmapRect = new Rect();
    //private Rect mCanvasRect = new Rect();
    //private float mLastY = -2000;
    
    public int selectedJulianDay = 0;
    
    // Pre-allocate and reuse
    private Time tempTime = new Time();
    private Paint p = new Paint();
    private Rect mRect = new Rect();
    private ColorFilter grayFilter;
    private ColorFilter selectedFilter;
    
    //private int mCellHeight;
    //private int mCellWidth;
    
    //List<Instance> mInstances;
    private int[] eventTextY = new int[7];  // init in drawBox, for drawEvents use
    private boolean startFromCurrMth = true;
    private int currMonthBoundary = 7;  // negative: all not current or from not current to current, positive: all current or from current to not current 
    
    public List<Instance> mInstances;
   
    public WeekInMonthView(Context context) {
    	super(context);
    }
    
    public void changeWeek(int julianDay, int selectedJulianDay, int currentMonth, CalendarConfig config) {
    	this.config = config;
    	this.mCurrentMonth = currentMonth;
    	this.mFirstJulianDay = julianDay;
    	this.selectedJulianDay = selectedJulianDay;
    	refreshCurrMonthArray();
    	mDrawEvents = false;
    	mInstances = null;
        init();
    	this.mRedrawScreen = true;
    	invalidate();
    }
    
    
    
    /**
     * Set first julian day of this week
     */
    /*
    public void setJulianDay(int julianDay) {
    	if (mFirstJulianDay != julianDay) {
	    	this.mFirstJulianDay = julianDay;
	    	refreshCurrMonthArray();
	    	this.mRedrawScreen = true;
	    	invalidate();
    	}
    }
    */
    
    /**
     * Set the current month for highlight (0 - 11)
     */
    public void setCurrentMonth(int month) {
    	if (mCurrentMonth != month) {
	    	this.mCurrentMonth = month;
	    	refreshCurrMonthArray();
	    	invalidate();
	    	//this.mRedrawScreen = true;
	    	//invalidate();
    	}
    }
    
    public void setConfig(CalendarConfig config) {
    	this.config = config;
    	mRedrawScreen = true;
    	invalidate();
    }
    
    public void setInstances(List<Instance> instances) {
    	mInstances = instances;
    	//if (!mRedrawScreen) {
    	//	drawEvents(mCanvas, p);
    	//}
    	//mRedrawScreen = true;
    	mDrawEvents = true;
    	invalidate();
    }
    
    private void refreshCurrMonthArray() {
		tempTime.setJulianDay(mFirstJulianDay);
		if (tempTime.month == mCurrentMonth) {
			startFromCurrMth = true;
		} else {
			startFromCurrMth = false;
		}
		currMonthBoundary = -1;
    	for (int i=1; i<7 && currMonthBoundary == -1; ++i) {
    		tempTime.monthDay++;
    		tempTime.normalize(true);
    		if ((startFromCurrMth && tempTime.month != mCurrentMonth) || 
    			(!startFromCurrMth && tempTime.month == mCurrentMonth)) {
    			currMonthBoundary = i;
    		}
    	}
    	if (currMonthBoundary == -1) {
    		currMonthBoundary = 7;
    	}
    	
    	//logger.info("refresh: " + mFirstJulianDay + ":" + startFromCurrMth + ":" + currMonthBoundary);
    }
    
    private void init() {
        //setFocusable(true);
        //setClickable(true);
        
        grayFilter = new PorterDuffColorFilter(
        	config.mMonthDayNotCurrMthColor, PorterDuff.Mode.SRC_ATOP
        );
        selectedFilter = new PorterDuffColorFilter(
        	config.mMonthDaySelectedColor, PorterDuff.Mode.SRC_ATOP
        );
        //setOnCreateContextMenuListener(this);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	setMeasuredDimension(View.MeasureSpec.getSize(widthMeasureSpec), config.cellHeight);
    }
    
    @Override
    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
        initCanvas(width, height);
        // If the size changed, then we should rebuild the bitmaps...
        //clearBitmapCache();
    }
    
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // No need to hang onto the bitmaps...
        //clearBitmapCache();
        if (mBitmap != null) {
            mBitmap.recycle();
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
    	if (config != null) {  // draw only when this view is completely initialized
    		final CalendarConfig lConfig = config;
    		
	    	if (mRedrawScreen || mDrawEvents) {
				if (mCanvas == null) {
					initCanvas(getWidth(), getHeight());
				}
	
	            // If we are zero-sized, the canvas will remain null so check again
	            if (mCanvas != null) {
	                // Clear the background
	                final Canvas bitmapCanvas = mCanvas;
	                if (mRedrawScreen) {
		                bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
		                drawWeek(bitmapCanvas);
		                mRedrawScreen = false;
	                }
	                if (mInstances != null) {
	                	drawEvents(bitmapCanvas, p);
	                	mDrawEvents = false;
	                } else if (mDrawEvents) {  // error condition
	            		Toast.makeText(getContext(), "Failed to get calendar events.", Toast.LENGTH_SHORT).show();
	                }
	            }
	        }
	
	        // If we are zero-sized, the bitmap will be null so guard against this
	        if (mBitmap != null) {
	        	if (selectedJulianDay == 0) {
		        	if (startFromCurrMth && currMonthBoundary == 7) {
	        			canvas.drawBitmap(mBitmap, 0, 0, null);
		        	} else if (!startFromCurrMth && currMonthBoundary == 7) {
	        	   		p.setColorFilter(grayFilter);
	        			canvas.drawBitmap(mBitmap, 0, 0, p);
		        	} else {
		        		if (!startFromCurrMth) {
		        	   		p.setColorFilter(grayFilter);
		        		} else {
		        			p.setColorFilter(null);
		        		}
		        		int boundaryX = lConfig.cellWidth + (currMonthBoundary-1) * (lConfig.cellWidth + lConfig.MONTH_DAY_GAP);
						mRect.set(0, 0, boundaryX, lConfig.cellHeight);
	        			canvas.drawBitmap(mBitmap, mRect, mRect, p);
	
	        			if (startFromCurrMth) {
		        	   		p.setColorFilter(grayFilter);
		        		} else {
		        			p.setColorFilter(null);
		        		}
	        			mRect.set(boundaryX, 0, canvas.getWidth(), lConfig.cellHeight);
	        			canvas.drawBitmap(mBitmap, mRect, mRect, p);
		        	}
	        	} else {
	        		mRect.set(0, 0, lConfig.cellWidth, lConfig.cellHeight);
	        		for (int i=0; i<7; ++i) {
	        			if (i+mFirstJulianDay == selectedJulianDay) {
	        				p.setColorFilter(selectedFilter);
	        			} else if (i < currMonthBoundary) {
			        		if (!startFromCurrMth) {
			        	   		p.setColorFilter(grayFilter);
			        		} else {
			        			p.setColorFilter(null);
			        		}
	        			} else if (i >= currMonthBoundary) {
			        		if (!startFromCurrMth) {
			        			p.setColorFilter(null);
			        		} else {
			        	   		p.setColorFilter(grayFilter);
			        		}
	        			}
	        			
	        			canvas.drawBitmap(mBitmap, mRect, mRect, p);
	        			mRect.offset(lConfig.cellWidth + lConfig.MONTH_DAY_GAP, 0);
	        		}
	        	}
	        }
    	}
    }
    
    @Override
    /**
     * This listener only captures the selected day when down
     * and rely on the MonthListView to clear selected state (clearSelect) when move 
     * and trigger action when up if needed
     */
    public boolean onTouchEvent(MotionEvent event) { 
    	//dumpEvent(event);
        int action = event.getAction();
        int actionCode = action & MotionEvent.ACTION_MASK;
        CalendarConfig lConfig = config;
    	switch (actionCode) {
    		case MotionEvent.ACTION_DOWN:
    			float clickX = event.getX(event.getPointerId(0));
    			((MonthListView)getParent()).downJulianDay = mFirstJulianDay + (int)Math.floor(clickX / (lConfig.cellWidth + lConfig.MONTH_DAY_GAP)); 
    			break;
    	}
    	
    	return false; // indicate event was handled
    }
    
    /** Show an event in the LogCat view, for debugging */
    /*
    private void dumpEvent(MotionEvent event) {
       String names[] = { "DOWN" , "UP" , "MOVE" , "CANCEL" , "OUTSIDE" ,
          "POINTER_DOWN" , "POINTER_UP" , "7?" , "8?" , "9?" };
       StringBuilder sb = new StringBuilder();
       int action = event.getAction();
       int actionCode = action & MotionEvent.ACTION_MASK;
       sb.append("event ACTION_" ).append(names[actionCode]);
       if (actionCode == MotionEvent.ACTION_POINTER_DOWN
             || actionCode == MotionEvent.ACTION_POINTER_UP) {
          sb.append("(pid " ).append(
          action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
          sb.append(")" );
       }
       sb.append("[" );
       for (int i = 0; i < event.getPointerCount(); i++) {
          sb.append("#" ).append(i);
          sb.append("(pid " ).append(event.getPointerId(i));
          sb.append(")=" ).append((int) event.getX(i));
          sb.append("," ).append((int) event.getY(i));
          if (i + 1 < event.getPointerCount())
             sb.append(";" );
       }
       sb.append("]" );
       logger.info(sb.toString());
    }
    */
    
    /**
     * Draw a page of calendar with fixed no. of weeks on given canvas of bitmap
     * 
     * @param topLeftJulianDay top left julian day of the page
     * @param currentMonth current month for highlight (0-11)
     * @param mToday today time object for highlight
     * @param mCellWidth cell width
     * @param mCellHeight cell height
     * @param canvas Canvas of bitmap for drawing
     * @param rect Rect object for reuse
     * @param viewCalendar time object for reuse
     * @param event text y position array for reuse
     * @param
     */
    private void drawWeek(Canvas canvas) {
    	//boolean isLandscape = view.getResources().getConfiguration().orientation
    	//	== Configuration.ORIENTATION_LANDSCAPE;

    	p.setColorFilter(null);
        
        // Get the day for the date at top left cell
    	Time tempDay = tempTime;
    	tempDay.setJulianDay(mFirstJulianDay);
    	tempDay.normalize(true);

        for (int column = 0; column < 7; column++) {
            drawBox(mFirstJulianDay + column, tempDay, column, canvas, p);
            
            tempDay.monthDay += 1;
            tempDay.normalize(true);
        }

        //drawGrid(canvas, mCellWidth, mCellHeight, p);
        //drawEvents(canvas, p);
    }
    
    /**
     * Draw a single box with calendar day text onto the canvas.
     * 
     * @param day The day (1-31).
     * @param day The current month (0-11).
     * @param mToday today time object for highlight
     * @param mCellWidth cell width
     * @param mCellHeight cell height
     * @param row The row of the box (0 - [CALENDAR_PAGE_WEEKS-1]).
     * @param column The column of the box (0-6).
     * @param canvas The canvas to draw on.
     * @param p The paint used for drawing.
     * @param r The rectangle used for each box.
     */
    private void drawBox(int julianDay, Time day, int column, Canvas canvas, Paint p) {
        boolean isToday = false;
        final CalendarConfig lConfig = config;
        
        if (day.monthDay == lConfig.today.monthDay && day.year == lConfig.today.year
                && day.month == lConfig.today.month) {
            isToday = true;
        }

        int y = 0;
        int x = column*(lConfig.MONTH_DAY_GAP + lConfig.cellWidth);

        mRect.left = x;
        mRect.top = y;
        mRect.right = x + lConfig.cellWidth;
        mRect.bottom = y + lConfig.cellHeight;

        // Adjust the left column, right column, and bottom row to leave
        // no border.
        if (column == 0) {
        	mRect.left = -1;
        } else if (column == 6) {
        	mRect.right += 2;
        }

        // Draw the cell contents (excluding monthDay number)
        int bgColor = lConfig.mMonthBgColor, textColor = lConfig.mMonthDayNumberColor;
        //if (day.month != mCurrentMonth) {
        //	bgColor = lConfig.mMonthOtherMonthBgColor;
        //	textColor = lConfig.mMonthOtherMonthDayNumberColor;
        //} else 
        if (isToday) {
           	//bgColor = mMonthTodayBgColor;
           	textColor = lConfig.mMonthTodayNumberColor;
        } else if (day.weekDay == Time.SUNDAY){
        	bgColor = lConfig.mMonthSundayBgColor;
           	textColor = lConfig.mMonthSundayNumberColor;
        } else if (day.weekDay == Time.SATURDAY){
        	bgColor = lConfig.mMonthSaturdayBgColor;
           	textColor = lConfig.mMonthSaturdayNumberColor;
        }
        p.setStyle(Style.FILL);
        p.setColor(bgColor);
        canvas.drawRect(mRect, p);

        //drawEvents(day, canvas, r, p, !isToday /*draw bb background*/);

        // Draw the monthDay number
        p.setStyle(Paint.Style.FILL);
        p.setAntiAlias(true);
        p.setTypeface(null);
        p.setTextSize(lConfig.MONTH_DAY_TEXT_SIZE);
        
        /*Drawing of day number is done here
         *easy to find tags draw number draw day*/
        p.setTextAlign(Paint.Align.LEFT);
        // center of text
        Paint.FontMetricsInt fm = p.getFontMetricsInt();
        int textX = mRect.left + lConfig.MONTH_DAY_TEXT_LEFT_MARGIN;
        int textY = (int) (mRect.top + Math.abs(fm.top) + lConfig.MONTH_DAY_TEXT_TOP_MARGIN); // bottom of text
        int textBottom = textY + Math.abs(fm.bottom) + lConfig.MONTH_DAY_TEXT_BOTTOM_MARGIN;

        // Draw today background
        if (isToday) {
            p.setColor(lConfig.mMonthTodayBgColor);
            mRect.bottom = textBottom;
            canvas.drawRect(mRect, p);
        }

        p.setColor(textColor);
        canvas.drawText(String.valueOf(day.monthDay), textX, textY, p);
        
        eventTextY[column] = textBottom;
        
        // Draw chinese calendar day
        p.setTextAlign(Paint.Align.RIGHT);
        p.setTextSize(lConfig.CHI_DAY_TEXT_SIZE);
        fm = p.getFontMetricsInt();
        textX = mRect.right - lConfig.CHI_DAY_TEXT_RIGHT_MARGIN;
        textY = (int) (mRect.top + Math.abs(fm.top) + lConfig.CHI_DAY_TEXT_TOP_MARGIN); // bottom of text
        //String chiDayText = new ChineseCalendar.SolarDate(
		//		day.year, day.month+1, day.monthDay).toLunarDate().getDayDesc();
        lConfig.lunarCalendar.setSolarDate(julianDay);
        String[] chiDayText = lConfig.lunarCalendar.getDayAbbr();
        if (isToday) {
        	p.setColor(lConfig.mMonthTodayChiNumberColor);
        } else if (chiDayText[1].equals(LunarCalendar.LUNAR_NORMAL)) {
            p.setColor(lConfig.mMonthChiDayNumberColor);
        } else {
        	p.setColor(lConfig.mMonthChiSDayNumberColor);
        }
        canvas.drawText(chiDayText[0], textX, textY, p);
    }

    /**
     * Draw the grid lines for the calendar
     * @param canvas The canvas to draw on.
     * @param p The paint used for drawing.
     */
    /*
    private static void drawGrid(Canvas canvas, int mCellWidth, int mCellHeight, Paint p) {
        p.setColor(mMonthBorderColor);
        p.setAntiAlias(false);

        final int width = canvas.getWidth();
        final int height = canvas.getHeight();

        for (int row = 0; row < CALENDAR_PAGE_WEEKS; row++) {
            int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1;
            canvas.drawLine(0, y, width, y, p);
        }
        for (int column = 1; column < 7; column++) {
            int x = column * (MONTH_DAY_GAP + mCellWidth) - 1;
            canvas.drawLine(x, WEEK_GAP, x, height, p);
        }
    }
    */

    private void drawEvents(Canvas canvas, Paint p) {
    	if (mInstances != null) {
	    	for (Instance instance: mInstances) {
	    		int startDay = instance.startDay;
	    		int endDay = instance.endDay;
	    		
	    		// The event may start before the week and/or end after the week
	    		if (startDay < mFirstJulianDay) {
	    			startDay = mFirstJulianDay;
	    		}
	    		if (endDay > mFirstJulianDay+6) {
	    			endDay = mFirstJulianDay+6;
	    		}
	    		
       			drawEvent(startDay, endDay, instance, canvas, p);
	    	}
    	}
    }
    
    /**
     * Draw an event within a week (startDay and endDay should be within the same row when calling this function)
     * 
     * @param week the week of event
     * @param startDay the start julian day of the event
     * @param endDay the end julian day of the event
     * @param instance the event object
     * @param canvas the canvas to draw on
     * @param p the paint to use
     * @param r the rect object for reuse
     */
    private void drawEvent(int startDay, int endDay, Instance instance, Canvas canvas, Paint p) {
    	final CalendarConfig lConfig = config;
    	int x = (startDay - mFirstJulianDay) * (lConfig.MONTH_DAY_GAP + lConfig.cellWidth);
    	int y = 0;
    	for (int i=startDay; i<=endDay; ++i) {
    		if (y < eventTextY[i-mFirstJulianDay]) {
    			y = eventTextY[i-mFirstJulianDay];
    		}
    	}

        p.setStyle(Paint.Style.FILL);
        p.setAntiAlias(true);
        p.setTypeface(null);
        p.setTextSize(lConfig.EVENT_TEXT_SIZE);
        p.setColor(instance.color);
        p.setTextAlign(Paint.Align.LEFT);

        Paint.FontMetricsInt fm = p.getFontMetricsInt();
        mRect.left = x;
        mRect.top = y + lConfig.EVENT_TEXT_TOP_MARGIN;
        mRect.right = (endDay - mFirstJulianDay) * (lConfig.MONTH_DAY_GAP + lConfig.cellWidth) + lConfig.cellWidth;
        int textY = y + lConfig.EVENT_TEXT_TOP_MARGIN + Math.abs(fm.top);
        mRect.bottom = textY + Math.abs(fm.bottom);

        if (mRect.bottom <= lConfig.cellHeight) {
	    	// Draw background if span more than a day
	        if (startDay < endDay) {
	        	p.setAlpha(50);
	        	canvas.drawRoundRect(new RectF(mRect), 6f, 6f, p);
	        	p.setAlpha(255);
	        }
	    	
	    	// Draw event text
	        mRect.right -= lConfig.EVENT_TEXT_RIGHT_MARGIN;
	        canvas.save();
	        canvas.clipRect(mRect);
	        canvas.drawText(instance.title, x + lConfig.EVENT_TEXT_LEFT_MARGIN, textY, p);
	        canvas.restore();
	        
	    	for (int i=startDay; i<=endDay; ++i) {
	   			eventTextY[i-mFirstJulianDay] = mRect.bottom;
	    	}
        }
    }
    
    // Re-create shadow bitmap and canvas for the new width and height
    private void initCanvas(int width, int height) {
        if (((mBitmap == null)
			|| mBitmap.isRecycled()
			|| (mBitmap.getHeight() != height)
			|| (mBitmap.getWidth() != width))
                && (width > 0) && (height > 0)) {
			if (mBitmap != null) {
				mBitmap.recycle();
			}
			mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
			mCanvas = new Canvas(mBitmap);
        }
    }
}
